#include "stdafx.h"
#include "Console.h"
#include "CVar.h"


static const uint CONSOLE_BACKGROUND_COLOR = 0x80000000;
static const uint CONSOLE_SEPARATOR_COLOR = 0x20ffffff;
static const float CONSOLE_TEXT_INDENT = 4.0f;
static const float CARET_BLINK_DELAY = 0.6f;


CConsole::CConsole()
: m_bVisible(true)
, m_line_pos(0)
, m_history_pos(0)
, m_caret_pos(0)
, m_caret_blink_time(0)
{

}

CConsole::~CConsole()
{
	//	destroy cvar's objects
	for (auto it = m_variables.begin(); it != m_variables.end(); ++it)
		delete it->second;
}

void CConsole::Show()
{
	m_bVisible = true;
}

void CConsole::Hide()
{
	m_bVisible = false;
	m_input_line.clear();
}

void CConsole::Toggle()
{
	!IsVisible() ? Show() : Hide();
}

bool CConsole::IsVisible() const
{
	return m_bVisible;
}

void CConsole::Render()
{
	if (IsVisible())
	{
		IFont* pFont = gEnv->pRender->GetFont();
		I2DRender* p2DRender = gEnv->pRender->Get2DRender();
		pFont->Flush();

		//	render background
		p2DRender->Set2DMode(1.0f, 1.0f);
		p2DRender->DrawQuad(0, 0, 1.0f, 0.5f, CONSOLE_BACKGROUND_COLOR);
		p2DRender->DrawQuad(0, 0.5f, 1.0f, 0.005f, CONSOLE_SEPARATOR_COLOR);
		p2DRender->Flush();

		//	render input line
		RenderLine(m_input_line.c_str(), 0);

		//	render text
		uint index = 1;
		int offset = max<int>(0, GetLinesCount() - m_line_pos - GetPageSize());
		for (auto it = m_lines.crbegin() + offset; it != m_lines.crend(); ++it, ++index)
		{
			const string& text = *it;
			if (!RenderLine(text.c_str(), index))	//	offscreen check
				break;
		}

		//	render caret
		RenderCaret();

		pFont->Flush();
	}
}

void CConsole::AddLine(const char* text)
{
	m_lines.push_back(text);
	ScrollToEnd();
}

void CConsole::ExecuteString(const char* text)
{
	assert(text);

	std::vector<string> args;
	string_utils::split(text, args);

	if (args.size() == 1)	//	console command
	{

		//	print command execution confirmation
		string confirmation = string_utils::format("\t%s", args[0].c_str());
		Log(confirmation.c_str());
	}
	else if (args.size() > 1)	//	console variable
	{
		ICVar* pVar = GetVariable(args[0].c_str());
		if (pVar)
		{
			string param;
			for (uint i = 1; i < args.size(); ++i)
				param += args[i];

			int var;
			if (string_utils::from_string(param.c_str(), var))
				pVar->Set(var);
			
			//	print cvar change confirmation
			string confirmation = string_utils::format("\t%s = %i", args[0].c_str(), pVar->GetInt());
			Log(confirmation.c_str());
		}
		else
			Warning("Unknown wariable: '%s'", args[0].c_str());
	}
}

void CConsole::RegisterVariable(const char* name, string& variable, const char* default_value)
{
	CVar* pCVar = new CVar(variable);
	RegisterVariable(name, pCVar);
	pCVar->Set(default_value);
}

void CConsole::RegisterVariable(const char* name, float& variable, float default_value)
{
	CVar* pCVar = new CVar(variable);
	RegisterVariable(name, pCVar);
	pCVar->Set(default_value);
}

void CConsole::RegisterVariable(const char* name, int& variable, int default_value)
{
	CVar* pCVar = new CVar(variable);
	RegisterVariable(name, pCVar);
	pCVar->Set(default_value);
}

void CConsole::UnregisterVariable(const char* name)
{
	TVariables::iterator it = m_variables.find(name);
	if (it != m_variables.end())
	{
		delete it->second;
		m_variables.erase(name);
	}
}

ICVar* CConsole::GetVariable(const char* name)
{
	return stl::find_in_map(m_variables, name, nullptr);
}

void CConsole::OnInputEvent(const SInputEvent& input_event)
{
	if (input_event.key_state != eKeyState_Pressed)
		return;

	if (input_event.event_type == eKey_Tilde && !input_event.autorepeat)
	{
		Toggle();
	}
	else if (input_event.character != 0 && string_utils::is_printable(input_event.character))
	{
		PrintCharacter(input_event.character);
	}
	else if (input_event.event_type == eKey_Enter)
	{
		CommitInputLine();
	}
	else if(input_event.event_type == eKey_Backspace)
	{
		EraseCharacter();
	}
	else if (input_event.event_type == eKey_PageUp)
	{
		int scroll_delta = input_event.ctrl ? GetPageSize() : 1;
		Scroll(-scroll_delta);
	}
	else if (input_event.event_type == eKey_PageDown)
	{
		int scroll_delta = input_event.ctrl ? GetPageSize() : 1;
		Scroll(scroll_delta);
	}
	else if (input_event.event_type == eKey_Home)
	{
		if (input_event.ctrl)
			ScrollToStart();
		else
			MoveCaretToStart();
	}
	else if (input_event.event_type == eKey_End)
	{
		if (input_event.ctrl)
			ScrollToEnd();
		else
			MoveCaretToEnd();
	}
	else if (input_event.event_type == eKey_Up)
	{
		ScrollHistory(-1);
	}
	else if (input_event.event_type == eKey_Down)
	{
		ScrollHistory(1);
	}
	else if (input_event.event_type == eKey_Left)
	{
		MoveCaret(-1);
	}
	else if (input_event.event_type == eKey_Right)
	{
		MoveCaret(1);
	}
}

bool CConsole::RenderLine(const char* text, uint index) const
{
	assert(text);

	if (*text)
	{
		IRender* pRender = gEnv->pRender;
		IFont* pFont = pRender->GetFont();

		uint y = GetHeight() - (index + 1)*pFont->GetLineSpacing() - 2;

		bool on_screen = (y >= 0);
		if (on_screen)
			pFont->RenderText(text, CONSOLE_TEXT_INDENT, static_cast<float>(y), Color::White);

		return on_screen;
	}
	return false;
}

void CConsole::RenderCaret()
{
	float blink_delta = gEnv->pTimer->GetTime(eTime_FrameStart) - m_caret_blink_time;
	int val = static_cast<int>(blink_delta/CARET_BLINK_DELAY);
	bool is_visible = (val % 2 == 0);
	if (is_visible)
	{
		string caret(m_caret_pos, ' ');
		caret.push_back('_');
		RenderLine(caret.c_str(), 0);
	}
}

void CConsole::RegisterVariable(const char* name, CVar* variable)
{
	assert(name);
	assert(variable);

	m_variables[name] = variable;
}

uint CConsole::GetHeight() const
{
	return gEnv->pRender->GetHeight()/2;
}

uint CConsole::GetPageSize() const
{
	IFont* pFont = gEnv->pRender->GetFont();
	return GetHeight()/pFont->GetLineSpacing() - 1;	//	ignore input line
}

uint CConsole::GetLinesCount() const
{
	return m_lines.size();
}

void CConsole::Scroll(int delta)
{
	if (GetLinesCount() > GetPageSize())
	{
		m_line_pos = clamp<int>(m_line_pos + delta, 0, GetLinesCount() - GetPageSize());
	}
}

void CConsole::ScrollToStart()
{
	m_line_pos = 0;
}

void CConsole::ScrollToEnd()
{
	if (GetLinesCount() > GetPageSize())
		m_line_pos = GetLinesCount() - GetPageSize();
}

void CConsole::MoveCaret(int delta)
{
	m_caret_pos = clamp<int>(m_caret_pos + delta, 0, m_input_line.size());
	ResetCaretBlinkTime();
}

void CConsole::MoveCaretToStart()
{
	m_caret_pos = 0;
	ResetCaretBlinkTime();
}

void CConsole::MoveCaretToEnd()
{
	m_caret_pos = m_input_line.size();
	ResetCaretBlinkTime();
}

void CConsole::ResetCaretBlinkTime()
{
	m_caret_blink_time = gEnv->pTimer->GetTime(eTime_FrameStart);
}

void CConsole::AddToHistory(const char* text)
{
	m_history.push_back(text);
	ResetHistoryPos();
}

void CConsole::ScrollHistory(int delta)
{
	if (!m_history.empty() && (delta < 0 || m_history_pos != m_history.size() - 1))
	{
		m_history_pos = clamp<int>(m_history_pos + delta, 0, m_history.size() - 1);
		m_input_line = m_history[m_history_pos];
	}
	else
		m_input_line.clear();

	MoveCaretToEnd();
}

void CConsole::ResetHistoryPos()
{
	if (!m_history.empty())
		m_history_pos = m_history.size();
}

void CConsole::PrintCharacter(char c)
{
	m_input_line.insert(m_caret_pos, 1, c);
	MoveCaret(1);
}

void CConsole::EraseCharacter()
{
	if (!m_input_line.empty() && m_caret_pos > 0)
	{
		uint size = m_input_line.size();
		m_input_line.erase(m_caret_pos - 1, 1);
		MoveCaret(-1);
	}
}

void CConsole::CommitInputLine()
{
	if (!m_input_line.empty())
	{
		AddLine(m_input_line.c_str());
		ExecuteString(m_input_line.c_str());
		AddToHistory(m_input_line.c_str());
		MoveCaretToStart();
		m_input_line.clear();
	}
}